// SPDX-License-Identifier: GPL-2.0
/*
 * UCSI debugfs interface
 *
 * Copyright (C) 2023 Intel Corporation
 *
 * Authors: Rajaram Regupathy <rajaram.regupathy@intel.com>
 *	    Gopal Saranya <saranya.gopal@intel.com>
 */
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/usb.h>

#include <asm/errno.h>

#include "ucsi.h"

static struct dentry *ucsi_debugfs_root;

static int ucsi_cmd(void *data, u64 val)
{
	struct ucsi *ucsi = data;
	int ret;

	memset(&ucsi->debugfs->response, 0, sizeof(ucsi->debugfs->response));
	ucsi->debugfs->status = 0;

	switch (UCSI_COMMAND(val)) {
	case UCSI_SET_CCOM:
	case UCSI_SET_UOR:
	case UCSI_SET_PDR:
	case UCSI_CONNECTOR_RESET:
	case UCSI_SET_SINK_PATH:
	case UCSI_SET_NEW_CAM:
	case UCSI_SET_USB:
	case UCSI_SET_POWER_LEVEL:
	case UCSI_READ_POWER_LEVEL:
	case UCSI_SET_PDOS:
		ucsi->message_in_size = 0;
		ret = ucsi_send_command(ucsi, val);
		break;
	case UCSI_GET_CAPABILITY:
	case UCSI_GET_CONNECTOR_CAPABILITY:
	case UCSI_GET_ALTERNATE_MODES:
	case UCSI_GET_CAM_SUPPORTED:
	case UCSI_GET_CURRENT_CAM:
	case UCSI_GET_PDOS:
	case UCSI_GET_CABLE_PROPERTY:
	case UCSI_GET_CONNECTOR_STATUS:
	case UCSI_GET_ERROR_STATUS:
	case UCSI_GET_PD_MESSAGE:
	case UCSI_GET_ATTENTION_VDO:
	case UCSI_GET_CAM_CS:
	case UCSI_GET_LPM_PPM_INFO:
		ucsi->message_in_size = sizeof(ucsi->debugfs->response);
		ret = ucsi_send_command(ucsi, val);
		memcpy(&ucsi->debugfs->response, ucsi->message_in, sizeof(ucsi->debugfs->response));
		break;
	default:
		ret = -EOPNOTSUPP;
	}

	if (ret < 0) {
		ucsi->debugfs->status = ret;
		return ret;
	}

	return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(ucsi_cmd_fops, NULL, ucsi_cmd, "0x%llx\n");

static int ucsi_resp_show(struct seq_file *s, void *not_used)
{
	struct ucsi *ucsi = s->private;

	if (ucsi->debugfs->status)
		return ucsi->debugfs->status;

	seq_printf(s, "0x%016llx%016llx\n", ucsi->debugfs->response.high,
		   ucsi->debugfs->response.low);
	return 0;
}
DEFINE_SHOW_ATTRIBUTE(ucsi_resp);

static int ucsi_peak_curr_show(struct seq_file *m, void *v)
{
	struct ucsi *ucsi = m->private;

	seq_printf(m, "%u mA\n", ucsi->connector->peak_current);
	return 0;
}
DEFINE_SHOW_ATTRIBUTE(ucsi_peak_curr);

static int ucsi_avg_curr_show(struct seq_file *m, void *v)
{
	struct ucsi *ucsi = m->private;

	seq_printf(m, "%u mA\n", ucsi->connector->avg_current);
	return 0;
}
DEFINE_SHOW_ATTRIBUTE(ucsi_avg_curr);

static int ucsi_vbus_volt_show(struct seq_file *m, void *v)
{
	struct ucsi *ucsi = m->private;

	seq_printf(m, "%u mV\n", ucsi->connector->vbus_voltage);
	return 0;
}
DEFINE_SHOW_ATTRIBUTE(ucsi_vbus_volt);

static ssize_t ucsi_message_out_write(struct file *file,
				      const char __user *data, size_t count, loff_t *ppos)
{
	struct ucsi *ucsi = file->private_data;
	int ret;

	char *buf __free(kfree) = memdup_user_nul(data, count);
	if (IS_ERR(buf))
		return PTR_ERR(buf);

	ucsi->message_out_size = min(count / 2, UCSI_MAX_MESSAGE_OUT_LENGTH);
	ret = hex2bin(ucsi->message_out, buf, ucsi->message_out_size);
	if (ret)
		return ret;

	return count;
}

static const struct file_operations ucsi_message_out_fops = {
	.open = simple_open,
	.write = ucsi_message_out_write,
	.llseek = generic_file_llseek,
};

void ucsi_debugfs_register(struct ucsi *ucsi)
{
	ucsi->debugfs = kzalloc(sizeof(*ucsi->debugfs), GFP_KERNEL);
	if (!ucsi->debugfs)
		return;

	ucsi->debugfs->dentry = debugfs_create_dir(dev_name(ucsi->dev), ucsi_debugfs_root);
	debugfs_create_file("command", 0200, ucsi->debugfs->dentry, ucsi, &ucsi_cmd_fops);
	debugfs_create_file("response", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_resp_fops);
	debugfs_create_file("peak_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_peak_curr_fops);
	debugfs_create_file("avg_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_avg_curr_fops);
	debugfs_create_file("vbus_voltage", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_vbus_volt_fops);
	debugfs_create_file("message_out", 0200, ucsi->debugfs->dentry, ucsi,
			    &ucsi_message_out_fops);
}

void ucsi_debugfs_unregister(struct ucsi *ucsi)
{
	if (IS_ERR_OR_NULL(ucsi) || !ucsi->debugfs)
		return;

	debugfs_remove_recursive(ucsi->debugfs->dentry);
	kfree(ucsi->debugfs);
}

void ucsi_debugfs_init(void)
{
	ucsi_debugfs_root = debugfs_create_dir("ucsi", usb_debug_root);
}

void ucsi_debugfs_exit(void)
{
	debugfs_remove(ucsi_debugfs_root);
}
